package au.edu.tardis.tardis_portal;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import edu.harvard.hul.ois.mets.Mets;
import edu.harvard.hul.ois.mets.helper.MetsReader;
import edu.harvard.hul.ois.mets.helper.MetsValidator;

import trdDatafile.jaxb.Datafile;
import trdDataset2.jaxb.Dataset;

public class ExperimentParser
{
	// HANDLE EXCEPTIONS PROPERLY

	private Document doc;

	// private String xmlns;

	public ExperimentParser(Document doc) throws SAXException, IOException, XPathExpressionException, ParserConfigurationException
	{

		this.doc = doc;

	}

	public Dataset getTARDISdataset(String xmlData)
	{
		Dataset dataset = new Dataset();

		try
		{

			JAXBContext jc = JAXBContext.newInstance(trdDataset2.jaxb.ObjectFactory.class);

			Unmarshaller unmarshaller = jc.createUnmarshaller();

			dataset = (Dataset) unmarshaller.unmarshal(new StringReader(xmlData));

		}
		catch (JAXBException e)
		{

		}

		return dataset;
	}

	public Datafile getTARDISdatafile(String xmlData)
	{
		Datafile datafile = new Datafile();

		try
		{

			JAXBContext jc = JAXBContext.newInstance(trdDatafile.jaxb.ObjectFactory.class);

			Unmarshaller unmarshaller = jc.createUnmarshaller();

			datafile = (Datafile) unmarshaller.unmarshal(new StringReader(xmlData));

		}
		catch (JAXBException e)
		{
			e.printStackTrace();
		}

		return datafile;
	}

	public ExperimentParser(String xmlString) throws Exception
	{
		DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		domFactory.setNamespaceAware(true); // never forget this!
		DocumentBuilder builder;
		try
		{
			builder = (DocumentBuilder) domFactory.newDocumentBuilder();
			this.doc = builder.parse(new InputSource(new StringReader(xmlString)));

			// this.xmlns = this.doc.getFirstChild().getNamespaceURI();
		}
		catch (Exception e)
		{
			throw new Exception("Error parsing file");
		}
		
		validateMETS(xmlString);

	}

	private void validateMETS(String xmlString) throws Exception
	{
		Mets mets = new Mets();
		
		mets.read(new MetsReader(new ByteArrayInputStream(xmlString.getBytes())));
		
		mets.validate(new MetsValidator());
	}

	public String getTechXML(String techMDid) throws Exception
	{
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();

		TransformerFactory tFactory = TransformerFactory.newInstance();

		StringReader sr = new StringReader("<?xml version='1.0' ?>"
		        + "<xsl:stylesheet version='2.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:mets='http://www.loc.gov/METS/'>"
		        + "<xsl:output method='xml' indent='yes'/>" + "<xsl:template match='/'>" + "<xsl:copy-of select=\"//mets:techMD[@ID='" + techMDid
		        + "']/mets:mdWrap/mets:xmlData/*\"/>" + "</xsl:template></xsl:stylesheet>");

		Transformer transformer = tFactory.newTransformer(new javax.xml.transform.stream.StreamSource(sr));

		transformer.transform(new javax.xml.transform.dom.DOMSource(doc), new javax.xml.transform.stream.StreamResult(byteOut));

		return byteOut.toString();
	}

	public String getFileLocation(String fileID) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:fileSec/METS:fileGrp/METS:file[@ID='" + fileID + "']/METS:FLocat/@xlink:href");

		return getStringResult(expr);
	}

	public String getFileName(String fileID) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:fileSec/METS:fileGrp/METS:file[@ID='" + fileID + "']/@OWNERID");

		String result = "";
		
		try{
			result = getStringResult(expr);
		}
		catch(Exception e)
		{
			return null;
		}
		
		return result;
	}

	public String getFileSize(String fileID) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:fileSec/METS:fileGrp/METS:file[@ID='" + fileID + "']/@SIZE");

		String result = "";
		
		try{
			result = getStringResult(expr);
		}
		catch(Exception e)
		{
			return null;
		}
		
		return result;
	}

	public void setFileLocation(String fileID, String fileLocation) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:fileSec/METS:fileGrp/METS:file[@ID='" + fileID + "']/METS:FLocat/@xlink:href");

		Object result = expr.evaluate(this.doc, XPathConstants.NODESET);

		NodeList nodes = (NodeList) result;

		nodes.item(0).setNodeValue(fileLocation);
	}

	public String getExperimentTitle() throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath
		        .compile("//METS:dmdSec[//METS:div[@TYPE='investigation']/@DMDID = @ID]/METS:mdWrap[@MDTYPE='MODS']//mods:title/text()");

		return getStringResult(expr);
	}

	public String getExperimentAbstract() throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath
		        .compile("//METS:dmdSec[//METS:div[@TYPE='investigation']/@DMDID = @ID]/METS:mdWrap[@MDTYPE='MODS']//mods:abstract/text()");

		return getStringResult(expr);
	}

	public ArrayList<String> getExperimentPDBIDs() throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath
		        .compile("//METS:dmdSec[//METS:div[@TYPE='investigation']/@DMDID = @ID]/METS:mdWrap[@MDTYPE='MODS']//mods:identifier[@type='pdb']/text()");

		return getStringArrayResult(expr);
	}

	public ArrayList<String> getExperimentRelationURLs() throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath
		        .compile("//METS:dmdSec[//METS:div[@TYPE='investigation']/@DMDID = @ID]/METS:mdWrap[@MDTYPE='MODS']//mods:url/text()");

		return getStringArrayResult(expr);
	}

	public String getDatasetTitle(String DMDID) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:dmdSec[@ID='" + DMDID + "']/METS:mdWrap[@MDTYPE='MODS']//mods:title/text()");

		return getStringResult(expr);
	}

	public String getAgentName(String role) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:metsHdr/METS:agent[@ROLE='" + role + "']/METS:name/text()");

		return getStringResult(expr);
	}

	public ArrayList<String> getAuthors() throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:dmdSec[//METS:div[@TYPE='investigation']/@DMDID = @ID]/METS:mdWrap[@MDTYPE='MODS']"
		        + "//mods:name/mods:namePart/text()");

		return getStringArrayResult(expr);
	}

	public ArrayList<String> getDatasetDMDIDs() throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:structMap/METS:div[@TYPE='investigation']//METS:div[@TYPE='dataset']" + "/@DMDID");

		return getStringArrayResult(expr);
	}

	public ArrayList<String> getDatasetADMIDs(String dmdid) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:structMap/METS:div[@TYPE='investigation']//METS:div[@TYPE='dataset' and @DMDID='" + dmdid + "']"
		        + "/@ADMID");

		return getStringArrayResult(expr);
	}

	public ArrayList<String> getFileADMIDs(String fileid) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());

		XPathExpression expr = xpath.compile("//METS:fileSec/METS:fileGrp/METS:file[@ID='" + fileid + "']/@ADMID");

		return getStringArrayResult(expr);
	}

	public ArrayList<String> getFileIDs(String datasetDMDID) throws Exception
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		xpath.setNamespaceContext(new PersonalNamespaceContext());
		XPathExpression expr = xpath.compile("//METS:div[@DMDID='" + datasetDMDID + "']" + "//METS:fptr/@FILEID");

		return getStringArrayResult(expr);
	}

	private ArrayList<String> getStringArrayResult(XPathExpression expr) throws Exception
	{
		Object result = expr.evaluate(this.doc, XPathConstants.NODESET);
		ArrayList<String> resultList = new ArrayList<String>();

		NodeList nodes = (NodeList) result;
		for (int i = 0; i < nodes.getLength(); i++)
		{
			String resultString = (String) nodes.item(i).getNodeValue();

			if (resultString.isEmpty())
			{
				throw new Exception("Error reading METS document");
			}

			resultList.add(resultString);

		}
		return resultList;
	}

	private String getStringResult(XPathExpression expr) throws Exception
	{
		Object result = expr.evaluate(this.doc, XPathConstants.STRING);

		String resultString = (String) result;

		if (resultString.isEmpty())
		{
			throw new Exception("Error reading METS document");
		}

		return resultString;
	}

	public void printOutput() throws Exception // TODO: Get rid of ties to files
	{

		try
		{
			System.out.println(getAgentName("DISSEMINATOR"));

			System.out.println(getAgentName("CREATOR"));

			System.out.println("\nExperiment Information:");
			System.out.println("Title:" + getExperimentTitle());
			System.out.println("Authors: ");
			for (String author : this.getAuthors())
			{
				System.out.print(author);
			}
			System.out.println("Abstract:\n" + getExperimentAbstract());

			for (String dmdid : getDatasetDMDIDs())
			{
				System.out.println("\n\tDataset Name: " + getDatasetTitle(dmdid));
				System.out.println("\tDataset Files: ");
				for (String fileid : getFileIDs(dmdid))
				{
					System.out.println("\t" + getFileLocation(fileid));
					try
					{
						for (String admid : getFileADMIDs(fileid))
						{
							System.out.println("test: " + admid);
							String techxml = getTechXML(admid);

							System.out.println(getTARDISdatafile(techxml).getDirectBeamXPos());

							System.out.println("\t File Metadata:\n\n" + techxml);

							System.out.println(new XslTranslator(techxml).getXmlns());
						}
					}
					catch (TransformerException e)
					{
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

				for (String amdid : getDatasetADMIDs(dmdid))
				{
					try
					{
						System.out.println("\tDataset Metadata:\n\n" + getTechXML(amdid));
					}
					catch (TransformerException e)
					{
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

			}
		}
		catch (XPathExpressionException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}

	private class PersonalNamespaceContext implements NamespaceContext
	{

		public String getNamespaceURI(String prefix)
		{
			if (prefix == null)
				throw new NullPointerException("Null prefix");
			else if ("mods".equals(prefix))
				return "http://www.loc.gov/mods/v3";
			else if ("METS".equals(prefix))
				return "http://www.loc.gov/METS/";
			else if ("mets".equals(prefix))
				return "http://www.loc.gov/METS/";
			else if ("xlink".equals(prefix))
				return "http://www.w3.org/1999/xlink";
			else if ("trd".equals(prefix))
				return "http://www.tardis.edu.au/schemas";
			else if ("xml".equals(prefix))
				return XMLConstants.XML_NS_URI;
			else
				return "http://www.loc.gov/METS/";
		}

		// This method isn't necessary for XPath processing.
		public String getPrefix(String uri)
		{
			throw new UnsupportedOperationException();
		}

		// This method isn't necessary for XPath processing either.
		public Iterator<?> getPrefixes(String uri)
		{
			throw new UnsupportedOperationException();
		}

	}

	/**
	 * @param filePath the name of the file to open. Not sure if it can accept URLs or just filenames. Path handling could be better, and buffer sizes
	 *            are hardcoded
	 */
	private static String readFileAsString(String filePath) throws java.io.IOException
	{
		StringBuffer fileData = new StringBuffer(1000);
		BufferedReader reader = new BufferedReader(new FileReader(filePath));
		char[] buf = new char[1024];
		int numRead = 0;
		while ((numRead = reader.read(buf)) != -1)
		{
			fileData.append(buf, 0, numRead);
		}
		reader.close();
		return fileData.toString();
	}

	public static void main(String[] args)
	{
		String resultString = "";
		try
		{
			String xmlString = readFileAsString("/Users/steve/ftp/staging/1/METS.xml");

			ExperimentParser e = new ExperimentParser(xmlString);

			e.printOutput();

		}
		catch (Exception e)
		{
			resultString = ":O";
			e.printStackTrace();
			System.out.println(e.getMessage());
		}

		System.out.println(resultString);
	}

}